using System;
using System.IO;
using System.Security.Cryptography;
using VMagicMirror.Buddy;
using UnityEngine;

namespace Baku.VMagicMirror.Buddy
{
    /// <summary>
    /// サブキャラのスクリプト編集を補助するために以下の3点セットを決め打ちフォルダに出力するやつ
    /// - <see cref="IRootApi"/>のあるアセンブリ
    /// - そのアセンブリのdoc commentのxml
    /// - #load して使える定型スクリプト
    /// </summary>
    /// <remarks>
    /// このスクリプトはユーザーがVSCodeでスクリプトを書くときの補助のみを目的としているものなので、
    /// 無くてもサブキャラ自体は動く
    /// </remarks>
    public class BuddyApiFileCopyInitializer : PresenterBase
    {
        private const string ScriptContent = 
@"// NOTE: This file is automatically generated. The file should not be editted.
#r "".\VMagicMirror.Buddy.dll""
using VMagicMirror.Buddy;

IRootApi Api = null;
";

        public override void Initialize()
        {
            // TODO: 以下の2点に注意
            // - ビルド版にxmlが含まれてるかどうか
            // - ビルド時にもxmlが出てるかどうかも要チェック

            // NOTE: dllは実際に動いているものを持ってくるが、xmlは別途SteamingAssetsから引っ張ってくる
            var dllFilePath = typeof(IRootApi).Assembly.Location;
            var xmlFilePath = Path.Combine(Application.streamingAssetsPath,
                Path.ChangeExtension(Path.GetFileName(dllFilePath), ".xml")
            );

            var destFolder = SpecialFiles.BuddyReferenceDataDirectory;

            Directory.CreateDirectory(destFolder);
            CopyFile(dllFilePath, Path.Combine(destFolder, Path.GetFileName(dllFilePath)));
            CopyFile(xmlFilePath, Path.Combine(destFolder, Path.GetFileName(xmlFilePath)));
            
            // Globals.csxの内容が想定と違ったら更新: こっちはdllほど複雑でもないので、ReadAllTextで素で評価してしまう
            var globalScriptPath = SpecialFiles.BuddyReferenceDataGlobalScriptPath;
            if (!File.Exists(globalScriptPath) || File.ReadAllText(globalScriptPath) != ScriptContent)
            {
                File.Delete(globalScriptPath);
                File.WriteAllText(globalScriptPath, ScriptContent);
            }
        }

        // NOTE: ハッシュ値を見る以外にも、VMMのバージョン情報ファイルを横付けしておく…といった方法も取れそう

        // ファイルを上書きコピーする。ただし、ハッシュ値が同一の場合、(たぶん)ファイル差分がないはずとみなしてコピーをスキップする
        private static void CopyFile(string sourcePath, string destPath)
        {
            if (!ShouldCopy(sourcePath, destPath))
            {
                return;
            }
            
            File.Copy(sourcePath, destPath, true);
        }
        
        private static bool ShouldCopy(string sourcePath, string destPath)
        {
            if (!File.Exists(sourcePath))
            {
                // 元ファイルがなければコピーできないのでfalseにするが、これはイレギュラーなのでログは出しておく
                LogOutput.Instance.Write($"WARN: Tried to copy file, but source file does not exist at '{sourcePath}'");
                return false;
            }
            
            return 
                !File.Exists(destPath) || 
                ComputeFileHash(sourcePath) != ComputeFileHash(destPath);
        }

        private static string ComputeFileHash(string filePath)
        {
            using var stream = File.OpenRead(filePath);
            using var sha256 = SHA256.Create();
            
            var hashBytes = sha256.ComputeHash(stream);
            return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
        }

    }
}
